/*
 * Copyright 2011 Dominik Pretzsch <dominik.pretzsch at gmail.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.blacksheep.jmoteextract.archive.callback;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.sevenzipjbinding.IArchiveOpenCallback;
import net.sf.sevenzipjbinding.IArchiveOpenVolumeCallback;
import net.sf.sevenzipjbinding.IInStream;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;

/**
 * <code>ArchiveOpenVolumeCallback</code> is provided to the <code>open()</code> method <br>
 * of <code>SevenZip-J-Binding</code> when opening multi-volume archives and <br>
 * acts as a callback.
 * 
 * @author Dominik Pretzsch <dominik.pretzsch at gmail.com>
 */
public class ArchiveOpenVolumeCallback implements IArchiveOpenVolumeCallback, 
                                                  IArchiveOpenCallback {

    /**
     * Cache for opened file streams
     */
    private Map<String, RandomAccessFile> openedRandomAccessFileList =
            new HashMap<String, RandomAccessFile>();
    /**
     * Name of the last volume returned by {@link #getStream(String)}
     */
    private String name;
    private static final Logger LOGGER =  Logger.getLogger(ArchiveOpenVolumeCallback.class.getName());

    /**
     * This method should at least provide the name of the last
     * opened volume (propID=PropID.NAME).
     * 
     * @see IArchiveOpenVolumeCallback#getProperty(PropID)
     */
    public Object getProperty(PropID propID) throws SevenZipException {
        switch (propID) {
            case NAME:
                return name;
        }
        return null;
    }

    /**
     * The name of the required volume will be calculated out of the
     * name of the first volume and a volume index. In case of RAR file,
     * the substring ".partNN." in the name of the volume file will
     * indicate a volume with id NN. For example:
     * <ul>
     * <li>test.rar - single part archive or multi-part archive with
     * a single volume</li>
     * <li>test.part23.rar - 23-th part of a multi-part archive</li>
     * <li>test.part001.rar - first part of a multi-part archive.
     * "00" indicates, that at least 100 volumes must exist.</li>
     * </ul>
     */
    public IInStream getStream(String filename) throws SevenZipException {
        LOGGER.log(Level.FINE, "Try to get a stream for {0}", filename);
        try {
            // We use caching of opened streams, so check cache first
            LOGGER.log(Level.FINE, "Try get cached stream, if any");
            RandomAccessFile randomAccessFile = openedRandomAccessFileList.get(filename);
            if (randomAccessFile != null) { // Cache hit.
                LOGGER.log(Level.FINE, "Found cached stream");
                // Move the file pointer back to the beginning
                // in order to emulating new stream
                randomAccessFile.seek(0);

                // Save current volume name in case getProperty() will be called
                name = filename;

                LOGGER.log(Level.FINE, "Returning cached stream");
                return new RandomAccessFileInStream(randomAccessFile);
            }

            // Nothing useful in cache. Open required volume.
            LOGGER.log(Level.FINE, "No cached stream found. Try to open");
            randomAccessFile = new RandomAccessFile(filename, "r");

            // Put new stream in the cache
            LOGGER.log(Level.FINE, "Put new stream in the cache");
            openedRandomAccessFileList.put(filename, randomAccessFile);

            // Save current volume name in case getProperty() will be called
            name = filename;
            
            LOGGER.log(Level.FINE, "Return new stream");
            return new RandomAccessFileInStream(randomAccessFile);
        } catch (FileNotFoundException fileNotFoundException) {
            LOGGER.log(Level.FINE, "File not found", fileNotFoundException);
            // Required volume doesn't exist. This happens if the volume:
            // 1. never exists. 7-Zip doesn't know how many volumes should
            //    exist, so it have to try each volume.
            // 2. should be there, but doesn't. This is an error case.

            // Since normal and error cases are possible,
            // we can't throw an error message
            return null; // We return always null in this case
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Close all opened streams
     */
    void close() throws IOException {
        LOGGER.log(Level.FINE, "Try to close stream");
        for (RandomAccessFile file : openedRandomAccessFileList.values()) {
            file.close();
        }
    }

    public void setCompleted(Long files, Long bytes) throws SevenZipException {
        // TODO: setCompleted() ?
        //throw new UnsupportedOperationException("Not supported yet.");
    }

    public void setTotal(Long files, Long bytes) throws SevenZipException {
        // TODO: setTotal() ?
        //throw new UnsupportedOperationException("Not supported yet.");
    }
}
